import java.io.*;   
import java.net.*;
import java.util.*;   



public class Barberia extends Thread{

	protected DatagramSocket socket=null;
	private boolean ocupado ; //usado para definir el estado de la barberia
	private String host;       // host local
	private Integer puerto;		// puerto local
	private Integer cantBarberias; // cant de barberias de la red broad-cast
	private String nombre;			//nombre de la barberia
	private  LinkedList<String> cola; //cola de clientes
	private int cantRespServ; // contador para la seleccion del cliente con mayor prioridad 
	private LamportReloj reloj; //reloj interno
	private boolean token;		//token utilizado para acceder a la region critica
	
	
	/**
	 * Constructor de clase. Se inicializan todos los atributos de la clase.
	 */
	public Barberia(String nombre,String host,Integer puerto,int cantServidores) throws IOException{
		this.nombre= nombre;
		InetAddress direccionIP=InetAddress.getByName(host);
		this.host=host;
		this.puerto=puerto;
		ocupado=false;
		cola=new LinkedList<String>();
		reloj = new LamportReloj();
		this.cantBarberias= cantServidores;
		this.cantRespServ=0;
		token=false;
		socket= new DatagramSocket(puerto);
	}

	/**
	 * Método que inicia el funcionamiento de la Barbería.
	 */
	public void iniciar(){
		String clienteProximo=new String();
		System.out.println(this.nombre + " esperando clientes...");
		while(true){
			String mensaje = new String();
			try{
				try {
					//Espero recibir un mensaje
					mensaje=new String (recibir());					
				} catch (IOException e) {
					mensaje="";
					e.printStackTrace();
				}					
				if (mensaje.startsWith("Cliente")){ 
					mensaje= mensaje.split("\\+")[0]+"-"+reloj.getRelojInterno()+mensaje.split("\\+")[1];
					cola.add(mensaje);
					System.out.println("Llego el: "+ mensaje.split("-")[0]+" en tiempo: "+ mensaje.split("-")[1]);					
					reloj.setRelojInterno(reloj.getRelojInterno()+1); // Incremento el reloj interno en 1 unidad	
					sincronizarRelojes(); // Mando a sincronizar los relojes de los demas servidores
				}
				if (mensaje.startsWith("1ro en cola?")){
					String hostDePeticion= mensaje.split("-")[1];
					int puertoDePeticion= Integer.parseInt(mensaje.split("-")[2].trim());					
					if (cola.isEmpty()){
						enviar("primero en cola+",hostDePeticion,puertoDePeticion);
					}
					else{
						enviar("primero en cola+"+cola.getFirst(),hostDePeticion,puertoDePeticion); //envio el primero de la cola
					}						
				}
				if (mensaje.startsWith("primero en cola")){
					this.cantRespServ++;
					String clienteExterno=mensaje.split("\\+")[1];  //Cliente id-reloj-host-puerto  ||   -host-puerto
					clienteProximo= elegirCliente(clienteProximo,clienteExterno); // Elijo el cliente con mayor prioridad
					if(cantRespServ==cantBarberias-1){
						cantRespServ=0;
						if(!clienteProximo.isEmpty()){
							notificarEleccion(clienteProximo);
						}
						else{
							enviarToken();
						}
					}
				}
				if(mensaje.startsWith("cliente elegido")){ 					
					if(token){
						if (!mensaje.split("-")[1].trim().isEmpty()){ 
							atender(mensaje);
						}
						this.cantRespServ=0;
						enviarToken();					
					}
					if(!cola.isEmpty() && cola.getFirst().contains(mensaje.split("-")[1].trim())){ // Pregunto si el cliente elegido es de mi cola
						System.out.print("Remuevo el "+ mensaje.split("-")[1].trim());
						if (Integer.parseInt(mensaje.split("-")[3].trim()) != this.puerto ){
							System.out.println(" -> Fue atendido por: "+ "Barberia "+ (Integer.parseInt(mensaje.split("-")[3].trim())-4000 +1 ) );
						}
						else{
							System.out.println(" -> Fue atendido por mi");
						}
						cola.removeFirst();
					}
				}				
				if(mensaje.startsWith("Actualice su reloj")){
					String s=mensaje.split("-")[1]; // reloj
					s=s.trim(); //quita espacios en blanco 
					reloj.setRelojInterno(reloj.sincronizarRelojes(Integer.parseInt(s)));
				}
				if (mensaje.startsWith("token") ){
					token=true;
					if (!this.ocupado){
						if (!cola.isEmpty()){
							clienteProximo=cola.getFirst();
						}
						else{
							clienteProximo="";
						}
						enviarPeticion(); // Preguntar por primeros en la cola
					}
					else{
						enviarToken(); 
					}
				}
			}catch(IOException e){
				System.out.println(e);
			}
		}	
	}
	
	
	
	
	/**
	 * Funcion que escoje el mejor de dos clientes, el clienteProximo (el mejor hasta el momento) y el que llega del mensaje (externo).
	 * @param clienteProximo cliente elegido hasta el momento.
	 * @param clienteExterno cliente externo proveniente de un mensaje.
	 * @return cliente elegido con mayor prioridad.
	 */
	public String elegirCliente(String clienteProximo,String clienteExterno){
		if (clienteProximo.isEmpty()){
			if (clienteExterno.split("-")[0].trim().isEmpty()){
				return clienteProximo;
			}
			else{
				return clienteExterno;
			}
		}
		else{
			if (clienteExterno.split("-")[0].trim().isEmpty()){
				return clienteProximo;
			}
			else{
				//comparo por tiempos de llegada (gana el tiempo menor)
				if(Integer.parseInt(clienteProximo.split("-")[1].trim())>Integer.parseInt(clienteExterno.split("-")[1].trim())){
					return clienteExterno;
				}
				else if(Integer.parseInt(clienteProximo.split("-")[1].trim())<Integer.parseInt(clienteExterno.split("-")[1].trim())){
					return clienteProximo;
				}
				else if(Integer.parseInt(clienteProximo.split("-")[1].trim())==Integer.parseInt(clienteExterno.split("-")[1].trim())){
					//comparo por id (gana el id mas chico)
					int idProximo=Integer.parseInt(clienteProximo.split("-")[0].trim().split("Cliente")[1].trim());
					int idExterno=Integer.parseInt(clienteExterno.split("-")[0].trim().split("Cliente")[1].trim());
					if(idProximo<idExterno){
							return clienteProximo;
					}
					else{
						return clienteExterno;
					}			
				}
			}
		}
		return clienteProximo;
	}
	
	
	/**
	 * Metodo que envia mensajes a todas las barberias informando el cliente elegido a atender.
	 * @param mensaje que contiene el cliente elegido
	 */
	public void notificarEleccion(String mensaje) throws IOException{
		for(int i=0;i<cantBarberias;i++){
			enviar("cliente elegido-"+mensaje.split("-")[0],"localhost",4000+i);
		}
	}
	
	/**
	 * Metodo que envia mensajes a todas las barberias informando que deben sincronizar sus relojes.
	 */
	public void sincronizarRelojes() throws IOException{
		for(int i=0;i<this.cantBarberias;i++){
			int puerto= 4000 +i;
			if(puerto!=this.puerto)
				enviar("Actualice su reloj-"+reloj.getRelojInterno(),"localhost",4000+i);
		}
	}
	
	/**
	 * Metodo que simula el atender de un cliente. Durmiendo el Thread un tiempo determinado
	 */
	public void run(){
		int m=3000;
		int n=7000;
		int tiempoDeAtencion = (int) Math.floor(Math.random()*(n-m+1)+m);  // Valor entre M y N, ambos incluidos.
		try {
			this.sleep(tiempoDeAtencion);
	} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		ocupado=false;
	}
	
	
	/**
	 * Metodo que simula el atender de un mensaje.
	 */
	public void atender(String mensaje) throws IOException{
		System.out.println(this.nombre +" Atendiendo al "+mensaje.split("-")[1]);
		ocupado=true;
		Thread t = new Thread(this);
		t.start();
		
	}
	
	/**
	 * Metodo que envia mensajes a todas las barberias preguntando por los primeros en cola de cada uno.
	 */	
	public void enviarPeticion() throws IOException{
		for (int i=0;i<cantBarberias;i++){
			String host= "localhost";
			int puerto= 4000 +i;
			if(puerto!=this.puerto)
				enviar("1ro en cola?",host,puerto);
		}		
	}
	
	/**
	 * Metodo que envia el token a la siguiente Barberia.
	 */
	public void enviarToken() throws IOException{
		token=false;
		if ((this.puerto-4000+1)==cantBarberias){
			enviar("token","localhost",4000);
		}
		else{
			enviar("token","localhost",this.puerto+1);
		}
	}
	
	/**
	 * Metodo que envia un mensaje a un host y puerto determinado.
	 * @param mensaje que se desea enviar
	 * @param host al que se desea enviar el mensaje
	 * @param puerto al que se desea enviar el mensaje
	 */
	public void enviar(String mensaje, String host, int port) throws IOException{
		byte [] buffer = new byte[256];
		InetAddress hostAddress = InetAddress.getByName(host);
		buffer =mensaje.getBytes();
		DatagramPacket pakete=new DatagramPacket(buffer,buffer.length,hostAddress,port);
		socket.send(pakete);
	}
	
	/**
	 * Metodo que recibe un mensaje de algun host y puerto .
	 * @return dato que recibe
	 */
	public String recibir() throws IOException{
		byte [] buffer = new byte[256];
		DatagramPacket pakete = new DatagramPacket(buffer, buffer.length);
		socket.receive(pakete);
		String dato = new String (pakete.getData(), 0, pakete.getData().length, "UTF-8");
		dato = dato +"-"+pakete.getAddress().getHostAddress()+"-"+pakete.getPort();
		return dato;		
	}
	
	/**
	 * Metodo que setea el valor del token.
	 * @param valor a setear el token
	 */
	public void setToken(boolean valor){
		this.token=valor;
	}


	
}

